﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.ComponentModel.Composition;
using Microsoft.VisualStudio.Utilities;
using Microsoft.VisualStudio.Text.Editor;
using Microsoft.VisualStudio.Editor;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.TextManager.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using System.Runtime.InteropServices;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Text;
using LuaLanguageExtension.Parsing;
using LuaLanguageExtension.Parsing.Search;
using PSLib.Parsing.Common;
using PSLib.Parsing.Common.Search;
using System.Windows.Media;
using PSLib.Parsing.Lua.Context;

namespace LuaLanguageExtension.Intellisense
{
    internal sealed class CommandFilter : IOleCommandTarget
    {
        public CommandFilter(IObjectModelProviderBroker objectModelProviderBroker, IWpfTextView textView, ICompletionBroker broker, IGlyphService glyphService)
        {
            this.currentSession = null;
            this.TextView = textView;
            this.Broker = broker;
            this.objectModelProviderBroker = objectModelProviderBroker;
            this.glyphService = glyphService;
        }

        private char GetTypeChar(IntPtr pvaIn)
        {
            return (char)(ushort)Marshal.GetObjectForNativeVariant(pvaIn);
        }

        public int Exec(ref Guid pguidCmdGroup, uint nCmdID, uint nCmdexecopt, IntPtr pvaIn, IntPtr pvaOut)
        {
            bool handled = false;
            int hresult = VSConstants.S_OK;

            // 1. Pre-process
            if (pguidCmdGroup == VSConstants.VSStd2K)
            {
                switch ((VSConstants.VSStd2KCmdID)nCmdID)
                {
                    case VSConstants.VSStd2KCmdID.AUTOCOMPLETE:
                    case VSConstants.VSStd2KCmdID.COMPLETEWORD:
                        handled = StartSession();
                        break;
                    case VSConstants.VSStd2KCmdID.RETURN:
                        handled = Complete(false);
                        break;
                    case VSConstants.VSStd2KCmdID.TAB:
                        handled = Complete(true);
                        break;
                    case VSConstants.VSStd2KCmdID.CANCEL:
                        handled = Cancel();
                        break;
                }
            }

            if (!handled) hresult = Next.Exec(pguidCmdGroup, nCmdID, nCmdexecopt, pvaIn, pvaOut);

            if (ErrorHandler.Succeeded(hresult))
            {
                if (pguidCmdGroup == VSConstants.VSStd2K)
                {
                    switch ((VSConstants.VSStd2KCmdID)nCmdID)
                    {
                        case VSConstants.VSStd2KCmdID.TYPECHAR:
                            char ch = GetTypeChar(pvaIn);
                            if (char.IsPunctuation(ch) || char.IsSeparator(ch) || char.IsSymbol(ch))
                            {
                                if (currentSession != null) Cancel();
                                else StartSession();
                            }
                            else
                            {
                                if (currentSession != null) Filter();
                                //else
                                //{
                                //    SnapshotPoint point = TextView.Caret.Position.BufferPosition - 1;
                                //    StartSession(point);
                                //}
                            }
                            break;
                        case VSConstants.VSStd2KCmdID.BACKSPACE:
                            Filter();
                            break;
                    }
                }
            }

            return hresult;
        }

        private void Filter()
        {
            if (currentSession != null && currentSession.SelectedCompletionSet != null)
            {
                currentSession.SelectedCompletionSet.SelectBestMatch();
                currentSession.SelectedCompletionSet.Recalculate();
            }
        }

        bool Cancel()
        {
            if (currentSession == null) return false;
            currentSession.Dismiss();
            return true;
        }

        bool Complete(bool force)
        {
            if (currentSession == null) return false;

            if (!currentSession.SelectedCompletionSet.SelectionStatus.IsSelected && !force)
            {
                currentSession.Dismiss();
                return false;
            }
            else
            {
                currentSession.Commit();
                return true;
            }
        }

        bool StartSession()
        {
            return StartSession(TextView.Caret.Position.BufferPosition);
        }
        bool StartSession(SnapshotPoint point)
        {
            if (currentSession != null) return false;

            SnapshotPoint caret = point;
            ITextSnapshot snapshot = caret.Snapshot;


            ObjectModelIntellisenseAcrossData acrossData = this.objectModelProviderBroker.GetProvider(point.Snapshot.TextBuffer).IntellisenseAcrossData;
            if (!Broker.IsCompletionActive(TextView))
            {
                currentSession = Broker.CreateCompletionSession(TextView, snapshot.CreateTrackingPoint(caret, PointTrackingMode.Positive), true);
            }
            else
            {
                currentSession = Broker.GetSessions(TextView)[0];
            }

            currentSession.Properties.AddProperty(typeof(ObjectModelIntellisenseAcrossData), acrossData);
            currentSession.Start();
            currentSession.Dismissed += (sender, args) => currentSession = null;

            return true;
        }

        public int QueryStatus(ref Guid pguidCmdGroup, uint cCmds, OLECMD[] prgCmds, IntPtr pCmdText)
        {
            if (pguidCmdGroup == VSConstants.VSStd2K)
            {
                switch ((VSConstants.VSStd2KCmdID)prgCmds[0].cmdID)
                {
                    case VSConstants.VSStd2KCmdID.AUTOCOMPLETE:
                    case VSConstants.VSStd2KCmdID.COMPLETEWORD:
                        prgCmds[0].cmdf = (uint)OLECMDF.OLECMDF_ENABLED | (uint)OLECMDF.OLECMDF_SUPPORTED;
                        return VSConstants.S_OK;
                }
            }
            return Next.QueryStatus(pguidCmdGroup, cCmds, prgCmds, pCmdText);
        }

        //public ITrackingSpan GetTokenTrackingSpan(TokenStream activeContainer, ElementPosition position, ITextSnapshot snapshot)
        //{
            ////Get current token
            //TokensStreamSearch search = new TokensStreamSearch(activeContainer.TokenStream);
            //RecognizedToken token = search.Find(position);
            //RecognizedToken prevToken = search.Get(token,SearchDirection.Backward);
            //if (token == null) return null;
            //if (prevToken != null && prevToken.Token.GetSpan(activeContainer.TokenStream).End == token.Token.GetPosition())
            //{
            //    token = prevToken;
            //}
            

            ////Calculate current token position
            //int tokenStartPosition = position.GetGlobalPosition(snapshot);
            //switch (token.Type)
            //{
            //    case RecognizedTokenType.Keyword:
            //    case RecognizedTokenType.Word:
            //        tokenStartPosition = token.Token.GetPosition().GetGlobalPosition(snapshot);
            //        break;
            //    case RecognizedTokenType.String:
            //    case RecognizedTokenType.Comment:
            //        return null;
            //}
            //Span span = new Span(tokenStartPosition, position.GetGlobalPosition(snapshot) - tokenStartPosition);

            //return snapshot.CreateTrackingSpan(span, SpanTrackingMode.EdgeInclusive);
            //return null;
        //}

      

        //public List<Completion> GetCompletions(TokenStream activeContainer, ElementPosition position)
        //{
        //    List<Completion> completions = new List<Completion>();

        //    foreach (var item in activeContainer.ContextProvider.GetContext(position))
        //    {
        //        ImageSource icon = null;
        //        if (item.Type == CompletionType.Field) icon = glyphService.GetGlyph(StandardGlyphGroup.GlyphGroupField, StandardGlyphItem.GlyphItemPublic);
        //        else icon = glyphService.GetGlyph(StandardGlyphGroup.GlyphGroupMethod, StandardGlyphItem.GlyphItemPublic);
        //        completions.Add(new Completion(item.DisplayText, item.ReplaceText, string.Empty, icon, string.Empty));
        //    } 
        //    return completions;
        //}

        public IWpfTextView TextView { get; private set; }
        public ICompletionBroker Broker { get; private set; }
        public IOleCommandTarget Next { get; set; }
        
        private ICompletionSession currentSession;
        private IObjectModelProviderBroker objectModelProviderBroker;
        private IGlyphService glyphService;
    }

}
